/**
 * 项目名：化工企业安全生产信息化管理平台
 * 日期：    2020-03-01 22:19:08
 * Copyright (c) 2015- -版权所有
 */

package com.joysuch.wwyt.edu.service.impl;

import cn.hutool.core.convert.Convert;
import com.joysuch.wwyt.common.enums.CommonTypes;
import com.joysuch.wwyt.common.repository.CommonTypeDao;
import com.joysuch.wwyt.common.service.CommonTypeService;
import com.joysuch.wwyt.core.common.util.PageDataUtils;
import com.joysuch.wwyt.core.common.util.PageDataUtils.PageDataConvert;
import com.joysuch.wwyt.core.exception.BusinessRestrictionsException;
import com.joysuch.wwyt.core.repository.BaseUserDao;
import com.joysuch.wwyt.core.service.BaseAttachmentRepositoryService;
import com.joysuch.wwyt.core.service.BizCodeGeneratorFacade;
import com.joysuch.wwyt.core.support.Context;
import com.joysuch.wwyt.edu.bean.*;
import com.joysuch.wwyt.edu.bean.EduQuestionListBean.SearchEduQuestion;
import com.joysuch.wwyt.edu.entity.EduQuestion;
import com.joysuch.wwyt.edu.entity.EduQuestionItem;
import com.joysuch.wwyt.edu.entity.dsl.QEduQuestion;
import com.joysuch.wwyt.edu.enums.DistributionQuestionTypes;
import com.joysuch.wwyt.edu.enums.QuestionTypes;
import com.joysuch.wwyt.edu.repository.EduExamUserAnswerDao;
import com.joysuch.wwyt.edu.repository.EduQuestionDao;
import com.joysuch.wwyt.edu.repository.EduQuestionItemDao;
import com.joysuch.wwyt.edu.repository.EduTestPaperQuestionDao;
import com.joysuch.wwyt.edu.service.EduQuestionService;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.LocalDate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import javax.transaction.Transactional;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;

@Service
@Transactional
public class EduQuestionServiceImpl implements EduQuestionService {

    @Autowired
    private EduQuestionDao dao;
    @Autowired
    private BizCodeGeneratorFacade bizCodeGeneratorFacade;
    @Autowired
    private BaseUserDao baseUserDao;
    @Autowired
    private CommonTypeService commonTypeService;
    @Autowired
    private EduQuestionItemDao eduQuestionItemDao;
    @Autowired
    private BaseAttachmentRepositoryService baseAttachmentRepositoryService;
    @Autowired
    private EduTestPaperQuestionDao eduTestPaperQuestionDao;
    @Autowired
    private JPAQueryFactory jpaFactory;
    @Autowired
    private CommonTypeDao commonTypeDao;

    @Autowired
    private EduExamUserAnswerDao eduExamUserAnswerDao;

    @Override
    public EduQuestion add(EduQuestionEditBean entity) {
        EduQuestion content = new EduQuestion();
        if (StringUtils.isNotBlank(entity.getCode())) {
            Assert.isTrue(dao.countOfCode(entity.getCode()) == 0, String.format("编号%s已经存在", entity.getCode()));
            content.setCode(entity.getCode());
        } else {
            content.setCode(bizCodeGeneratorFacade.shiTi());
        }
        content.setCreateUserName(baseUserDao.getBaseUserNameById(Context.getCurrentUserId()));
        BeanUtils.copyProperties(entity, content, "id", "code");
        if (entity.getAttachment() != null) {
            content.setAttachmentId(entity.getAttachment().getId());
        }
        dao.save(content);
        if (entity.getItems() != null) {
            saveItems(entity.getItems(), content);
        }
        if (entity.getQuestionTypeId() != null) {
            commonTypeService.updateTypeCount(CommonTypes.QUESTION, entity.getQuestionTypeId());
        }
        return content;
    }

    private void saveItems(List<EduQuestionItemEditBean> items, EduQuestion content) {
        List<EduQuestionItem> list = new ArrayList<EduQuestionItem>(items.size());
        int index = 0;
        for (EduQuestionItemEditBean bean : items) {
            if (bean.getId() != null) {
                // 选项已经存在,兼容修改
                EduQuestionItem one = eduQuestionItemDao.getOne(bean.getId());
                one.setChoiceCode(bean.getChoiceCode());
                one.setContent(bean.getContent());
                one.setIndexNo(index++);
                one.setQuestionId(content.getId());
                list.add(one);
            } else {
                EduQuestionItem one = new EduQuestionItem();
                one.setChoiceCode(bean.getChoiceCode());
                one.setContent(bean.getContent());
                one.setIndexNo(index++);
                one.setQuestionId(content.getId());
                list.add(one);
            }
        }
        eduQuestionItemDao.saveAll(list);
    }

    @Override
    public void update(EduQuestionEditBean entity) {
        EduQuestion content = dao.findById(entity.getId()).get();

        if (!entity.getType().equals(content.getType())) {
            int count = eduTestPaperQuestionDao.countByQuestionId(entity.getId());
            if (count > 0) {
                throw new IllegalArgumentException("该试题已成为试卷试题不能修改类型");
            }
        }

//        Assert.isTrue(eduExamUserAnswerDao.countByQuestionId(entity.getId()) == 0, String.format("该考题已经被其他关联考试引用，不能修改"));

        // Assert.notNull(entity.getCode(), "编号不能为空");
        // Assert.isTrue(dao.countOfCodeExcludeId(entity.getCode(), entity.getId()) == 0,
        // String.format("编号%s已经存在", entity.getCode()));
        // content.setCode(entity.getCode());
        Long oldId = content.getQuestionTypeId();
        if (entity.getAttachment() != null) {
            content.setAttachmentId(entity.getAttachment().getId());
        }
        BeanUtils.copyProperties(entity, content, "id", "code");
        dao.save(content);
        clearRemovedItems(content.getId(), entity.getItems());
        if (entity.getItems() != null) {
            saveItems(entity.getItems(), content);
        }
        if (!Objects.equals(entity.getQuestionTypeId(), oldId)) {
            commonTypeService.updateTypeCount(CommonTypes.QUESTION, entity.getQuestionTypeId());
            commonTypeService.updateTypeCount(CommonTypes.QUESTION, oldId);
        }
    }

    private void clearRemovedItems(Long questionId, List<EduQuestionItemEditBean> items) {
        List<Long> ids = null;
        if (items == null) {
            ids = Collections.EMPTY_LIST;
        } else {
            ids = items.stream().map(EduQuestionItemEditBean::getId).filter(Objects::nonNull)
                    .collect(Collectors.toList());
        }
        if (!ids.isEmpty()) {
            eduQuestionItemDao.clearRemovedItems(questionId, ids);
        } else {
            eduQuestionItemDao.clearRemovedItems(questionId);
        }
    }

    @Override
    public Page<EduQuestionListBean> findByPage(Pageable page, SearchEduQuestion condition) {
        Predicate predicate = prepareListSearchCondition(condition);

        Page<EduQuestion> data = predicate == null ? dao.findAll(page) : dao.findAll(predicate, page);

        return PageDataUtils.convertPageData(data, new PageDataConvert<EduQuestionListBean, EduQuestion>() {

            @Override
            public EduQuestionListBean convert(EduQuestion f) {
                EduQuestionListBean bean = new EduQuestionListBean();
                BeanUtils.copyProperties(f, bean);
                bean.setQuestionTypeName(commonTypeService.getNameById(f.getQuestionTypeId()));
                bean.setTypeName(QuestionTypes.findNameByCode(f.getType()));
                bean.setAnswer(f.getAnswer());
                return bean;
            }

        });
    }

    private Predicate prepareListSearchCondition(SearchEduQuestion condition) {
        Predicate p = null;
        QEduQuestion q = QEduQuestion.eduQuestion;
        // 试题名称
        if (condition.getNameOrCode() != null) {
            p = ExpressionUtils.and(p, ExpressionUtils.or(q.name.contains(condition.getNameOrCode()),
                    q.code.contains(condition.getNameOrCode())));
        }
        // 题型
        if (condition.getType() != null) {
            p = ExpressionUtils.and(p, q.type.eq(condition.getType()));
        }
        // 试题类型
        if (condition.getQuestionTypeId() != null) {
            p = ExpressionUtils.and(p, q.questionTypeId.eq(condition.getQuestionTypeId()));
        }
        // 创建时间
        if (condition.getCreateTime() != null) {
            LocalDate start = LocalDate.fromDateFields(condition.getCreateTime());
            Date end = start.plusDays(1).toDate();
            p = ExpressionUtils.and(p, ExpressionUtils.and(q.createTime.goe(start.toDate()), q.createTime.lt(end)));
        }
        if (condition.getCreateUserName() != null) {
            p = ExpressionUtils.and(p, q.createBy.in(getPeopleIdList(condition.getCreateUserName())));
        }
        return p;
    }

    private List<Long> getPeopleIdList(String name) {
        List<Long> uList = new ArrayList<>();
        if (StringUtils.isNotBlank(name)) {
            uList = baseUserDao.getUserIdByNameContains(name);

        }
        return uList;
    }

    @Override
    public void delete(Long id) throws BusinessRestrictionsException {
        // 校验业务逻辑是否允许删除。如本数据被其他业务关联，删除后导致其他业务不可用。不能删除时将抛出业务异常
        checkBussinessRestrictions(id);
        EduQuestion question = dao.getOne(id);
        dao.deleteById(id);
        commonTypeService.updateTypeCount(CommonTypes.QUESTION, question.getQuestionTypeId());
    }

    /**
     * 校验业务逻辑是否允许删除。如本数据被其他业务关联，删除后导致其他业务不可用
     *
     * @param id 要删除的业务ID
     * @throws BusinessRestrictionsException 抛出的业务异常
     */
    private void checkBussinessRestrictions(Long id) throws BusinessRestrictionsException {
        int count = eduTestPaperQuestionDao.countByQuestionId(id);
        if (count > 0) {
            throw new IllegalArgumentException("该试题已被试卷绑定，请先解绑");
        }
    }

    @Override
    public EduQuestion findById(Long id) {
        return dao.findById(id).orElse(null);
    }

    @Override
    public EduQuestionDetailBean getById(Long id) {
        EduQuestion entity = findById(id);
        if (entity == null) {
            return null;
        }
        EduQuestionDetailBean bean = new EduQuestionDetailBean();
        BeanUtils.copyProperties(entity, bean);
        bean.setQuestionTypeName(commonTypeService.getNameById(entity.getQuestionTypeId()));
        if (entity.getAttachmentId() != null) {
            bean.setAttachment(baseAttachmentRepositoryService.findByIdIntoBean(entity.getAttachmentId()));
        }
        bean.setTypeName(QuestionTypes.findNameByCode(entity.getType()));
        bean.setItems(queryQuestionItems(entity.getId()));
        return bean;
    }

    private List<EduQuestionItemEditBean> queryQuestionItems(Long id) {
        List<EduQuestionItem> list = eduQuestionItemDao.findByQuestionIdOrderByIndexNoAsc(id);
        return list.stream().map(this::convertItem).collect(Collectors.toList());
    }

    EduQuestionItemEditBean convertItem(EduQuestionItem item) {
        EduQuestionItemEditBean bean = new EduQuestionItemEditBean();
        bean.setId(item.getId());
        bean.setQuestionId(item.getQuestionId());
        bean.setChoiceCode(item.getChoiceCode());
        bean.setContent(item.getContent());
        bean.setIndexNo(item.getIndexNo());
        return bean;
    }

    @Override
    public List<EduQuestion> randomSelect(List<EduRandomSelectQuestionConfig> configs) {
        if (configs.isEmpty()) {
            return Collections.EMPTY_LIST;
        }
        List<QuestionSelectWrapper> list = randomSelectWrapper(configs);
        return list.stream().map(e -> e.q).collect(Collectors.toList());
    }

    public List<QuestionSelectWrapper> randomSelectWrapper(List<EduRandomSelectQuestionConfig> configs) {
        if (configs.isEmpty()) {
            return Collections.EMPTY_LIST;
        }
        configs = configs.stream().filter(c -> c.getQuestionCount() > 0).collect(Collectors.toList());
        QEduQuestion q = QEduQuestion.eduQuestion;
        // 先选出所有被使用的分类ID
        List<Object[]> typeIdList = jpaFactory.selectDistinct(q.questionTypeId, q.type, q.id.count()).from(q)
                .groupBy(q.questionTypeId, q.type).createQuery().getResultList();
        Map<String, Long> typeCountMap = new HashMap<>();
        typeIdList.forEach(e -> typeCountMap.put(e[0] + "_" + e[1], (Long) e[2]));

        MultiValueMap<EduRandomSelectQuestionConfig, QuestionSelectCounter> configSelectMap = new LinkedMultiValueMap<>();

        Map<String, QuestionSelectCounter> selectTypeMap = filterQuestionTypeIdList(configs, typeCountMap,
                configSelectMap);

        Set<Long> selectedCatagoryIds = new HashSet<>();

        selectTypeMap.forEach((k, v) -> selectedCatagoryIds.add(v.questionTypeId));

        List<EduQuestion> questions = jpaFactory.selectDistinct(q).from(q)
                .where(q.questionTypeId.in(selectedCatagoryIds)).createQuery().getResultList();

        MultiValueMap<String, EduQuestion> questionMap = new LinkedMultiValueMap<>();

        questions.forEach(qt -> questionMap.add(createTypeKey(qt.getQuestionTypeId(), qt.getType()), qt));

        List<EduQuestion> result = new ArrayList<>(
                configs.stream().mapToInt(EduRandomSelectQuestionConfig::getQuestionCount).sum());
        selectTypeMap.forEach((k, v) -> {
            List<EduQuestion> qList = questionMap.get(k);
            if (qList.size() < v.count) {
                throw new IllegalArgumentException("随机生成试卷失败: 题库在生成期间发生了变化，导致题目数量不足");
            }
            Collections.shuffle(qList);
            result.addAll(qList.subList(0, v.count));
        });
        // 将题目分配到配置
        List<QuestionSelectWrapper> wrapList = wrapQuestions(result, configSelectMap, configs);
        Collections.sort(wrapList, new Comparator<QuestionSelectWrapper>() {

            @Override
            public int compare(QuestionSelectWrapper o1, QuestionSelectWrapper o2) {
                if (o1.configIndex > o2.configIndex) {
                    return 1;
                } else if (o1.configIndex < o2.configIndex) {
                    return -1;
                }
                return 0;
            }
        });
        return wrapList;
    }

    private List<QuestionSelectWrapper> wrapQuestions(List<EduQuestion> result,
                                                      MultiValueMap<EduRandomSelectQuestionConfig, QuestionSelectCounter> configSelectMap,
                                                      List<EduRandomSelectQuestionConfig> configList) {
        List<QuestionSelectWrapper> list = new ArrayList<EduQuestionServiceImpl.QuestionSelectWrapper>(result.size());
        MultiValueMap<String, EduQuestion> qm = new LinkedMultiValueMap<>();
        result.forEach(q -> qm.add(createTypeKey(q.getQuestionTypeId(), q.getType()), q));

        configSelectMap.forEach((config, l) -> {
            int configIndex = configList.indexOf(config);
            for (QuestionSelectCounter c : l) {
                List<EduQuestion> ql = qm.get(createTypeKey(c.questionTypeId, c.type));
                if (!CollectionUtils.isEmpty(ql)) {
                    for (int i = 0; i < c.count; i++) {
                        QuestionSelectWrapper w = new QuestionSelectWrapper();
                        // 添加后从中移除，防止重复添加
                        w.q = ql.remove(0);
                        w.config = config;
                        w.configIndex = configIndex;
                        list.add(w);
                    }
                }
            }
        });
        return list;
    }

    Map<String, QuestionSelectCounter> filterQuestionTypeIdList(List<EduRandomSelectQuestionConfig> configs,
                                                                Map<String, Long> typeCountMap,
                                                                MultiValueMap<EduRandomSelectQuestionConfig, QuestionSelectCounter> configSelectMap) {
        Map<String, QuestionSelectCounter> selectedMap = new HashMap<>();

        // 首先处理确定分类的确定题型
        processSpecifiedCatagoryAndTypeList(configs, typeCountMap, selectedMap, configSelectMap);

        // 再处理非随机分类确定题型 + 随机分类确定题型
        List<EduRandomSelectQuestionConfig> partRandomList = configs.stream()
                .filter(c -> c.getCatagory() != 0L && isRandomQuestionType(c.getQuestionType())
                        || c.getCatagory() == 0L && !isRandomQuestionType(c.getQuestionType()))
                .collect(Collectors.toList());
        processPartRandomList(partRandomList, typeCountMap, selectedMap, configSelectMap);

        // 最后处理完全随机的类型
        List<EduRandomSelectQuestionConfig> fullyRandomList = configs.stream()
                .filter(c -> c.getCatagory() == 0L && isRandomQuestionType(c.getQuestionType()))
                .collect(Collectors.toList());
        processFullyRandomList(fullyRandomList, typeCountMap, selectedMap, configSelectMap);

        return selectedMap;
    }

    public void removeZg(List<EduRandomSelectQuestionConfig> configs, Map<String, Long> typeCountMap) {
        List<String> removeKeyList = new ArrayList<>();
        for (EduRandomSelectQuestionConfig config : configs) {
            // 删除 主观题
            if (config.getQuestionType().equals(DistributionQuestionTypes.RANDOM_NOT_ZG_CHOICE.getCode())) {
                Iterator<String> it = typeCountMap.keySet().iterator();
                while (it.hasNext()) {
                    String key = it.next();
                    if (key.contains(QuestionTypes.ZG_CHOICE.getCode())) {
                        removeKeyList.add(key);
                    }
                }
            }
            if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(removeKeyList)) {
                removeKeyList.forEach(typeCountMap::remove);
            }
        }
    }

    private void processSpecifiedCatagoryAndTypeList(List<EduRandomSelectQuestionConfig> configs,
                                                     Map<String, Long> typeCountMap, Map<String, QuestionSelectCounter> selectedMap,
                                                     MultiValueMap<EduRandomSelectQuestionConfig, QuestionSelectCounter> configSelectMap) {
        for (EduRandomSelectQuestionConfig config : configs) {
            checkQuestionTypeCount(config, typeCountMap);
            String catagory = commonTypeDao.getNameById(config.getCatagory());
            String questionType = QuestionTypes.findNameByCode(config.getQuestionType());
            String key = createTypeKey(config.getCatagory(), config.getQuestionType());
            if (config.getCatagory() != 0L && !isRandomQuestionType(config.getQuestionType())) {
                Long count = typeCountMap.get(key);
                if (count == null) {
                    throw new IllegalArgumentException("分类" + catagory + "不存在" + questionType);
                }
                QuestionSelectCounter selected = selectedMap.computeIfAbsent(key, (k) -> {
                    QuestionSelectCounter s = new QuestionSelectCounter();
                    s.type = config.getQuestionType();
                    s.questionTypeId = config.getCatagory();
                    return s;
                });

                if (config.getQuestionCount() + selected.count > count) {
                    throw new IllegalArgumentException("分类" + catagory + "，题型" + questionType
                            + "题目数量只有" + count + "题，小于需求数量，无法生成");
                }
                selected.count += config.getQuestionCount().longValue();
                // 更新存量题源
                typeCountMap.put(key, count - config.getQuestionCount().longValue());

                updateConfigSelectMap(configSelectMap, config, selected);
            }
        }
    }

    private void updateConfigSelectMap(
            MultiValueMap<EduRandomSelectQuestionConfig, QuestionSelectCounter> configSelectMap,
            EduRandomSelectQuestionConfig config, QuestionSelectCounter selected) {
        // 更新相关配置 选择的题目类型数
        configSelectMap.compute(config, (k, v) -> {
            if (v == null) {
                List<QuestionSelectCounter> list = new ArrayList<>(4);
                list.add(selected);
                return list;
            } else {
                int i = v.indexOf(selected);
                if (i != -1) {
                    v.get(i).count += selected.count;
                } else {
                    v.add(selected);
                }
                return v;
            }
        });
    }

    private void processFullyRandomList(List<EduRandomSelectQuestionConfig> fullyRandomList,
                                        Map<String, Long> typeCountMap, Map<String, QuestionSelectCounter> selectedMap,
                                        MultiValueMap<EduRandomSelectQuestionConfig, QuestionSelectCounter> configSelectMap) {
        Random r = new Random();
        for (EduRandomSelectQuestionConfig config : fullyRandomList) {
            checkQuestionTypeCount(config, typeCountMap);
            List<String> keyList = new ArrayList(typeCountMap.keySet());
            int needCount = config.getQuestionCount();
            // 包含主观题
            if (config.getQuestionType().equals(DistributionQuestionTypes.RANDOM_CONTAIN_ZG_CHOICE.getCode())) {
                // 主观题
                Map<String, Long> zgMap = typeCountMap.entrySet().stream().filter(v -> v.getKey().contains(QuestionTypes.ZG_CHOICE.getCode())).collect(Collectors.toMap(
                        Entry::getKey,
                        Entry::getValue
                ));
                List<String> zgKeyList = new ArrayList(zgMap.keySet());
                long count = zgMap.values().stream().mapToLong(Long::longValue).sum();
                // 随机需要几个主观题
                int num = r.nextInt(needCount);
                if (num > count) {
                    num = Convert.toInt(count);
                }
                if (num == 0) {
                    num = 1;
                }

                // 记录随机分配前的主观题数据
                Map<String, Long> oldZgMap = new HashMap<>(zgMap);
                // 随机分配主观题
                processFullyRandomList(num, zgKeyList, config, zgMap, selectedMap, configSelectMap);
                needCount = needCount - num;

                // 更新typeCount数据
                updateExistsCountAfterProcessRandomContainZg(oldZgMap, zgMap, typeCountMap);

                // 非主观题
                Map<String, Long> notZgMap = typeCountMap.entrySet().stream().filter(v -> !v.getKey().contains(QuestionTypes.ZG_CHOICE.getCode())).collect(Collectors.toMap(
                        Entry::getKey,
                        Entry::getValue
                ));
                keyList = new ArrayList(notZgMap.keySet());
                // 记录随机分配前的非主观题数据
                Map<String, Long> oldNotZgMap = new HashMap<>(notZgMap);
                // 随机分配
                needCount = processFullyRandomList(needCount, keyList, config, notZgMap, selectedMap, configSelectMap);
                // 更新typeCount数据
                updateExistsCountAfterProcessRandomContainZg(oldNotZgMap, notZgMap, typeCountMap);
            }
            if (config.getQuestionType().equals(DistributionQuestionTypes.RANDOM_NOT_ZG_CHOICE.getCode())) {
                // 非主观题
                Map<String, Long> map = typeCountMap.entrySet().stream().filter(v -> !v.getKey().contains(QuestionTypes.ZG_CHOICE.getCode())).collect(Collectors.toMap(
                        Entry::getKey,
                        Entry::getValue
                ));
                keyList = new ArrayList(map.keySet());
                // 随机分配 不包含 主观题
                needCount = processFullyRandomList(needCount, keyList, config, map, selectedMap, configSelectMap);
            }

            if (needCount > 0) {
                throw new IllegalArgumentException("完全随机分配失败，题目数量小于需求数量，无法生成");
            }
        }
    }

    private void updateExistsCountAfterProcessRandomContainZg(Map<String, Long> oldZgMap, Map<String, Long> zgMap, Map<String, Long> typeCountMap) {
        oldZgMap.forEach((k, v) -> {
            Long zgSurplusCount = zgMap.get(k);
            if (zgSurplusCount == null) {
                typeCountMap.remove(k);
            } else {
                long useCount = v - zgSurplusCount;
                Long typeCount = typeCountMap.get(k);
                if (typeCount != null && useCount > 0) {
                    if (typeCount - useCount > 0) {
                        typeCountMap.put(k, typeCount - useCount);
                    } else {
                        typeCountMap.remove(k);
                    }
                }
            }
        });
    }


    /**
     * 随机主观题
     */
    private Integer processFullyRandomList(Integer needCount, List<String> keyList, EduRandomSelectQuestionConfig config,
                                           Map<String, Long> typeCountMap, Map<String, QuestionSelectCounter> selectedMap,
                                           MultiValueMap<EduRandomSelectQuestionConfig, QuestionSelectCounter> configSelectMap) {
        Random r = new Random();
        while (needCount > 0 && typeCountMap.size() > 0) {
            int keyIndex = r.nextInt(keyList.size());
            String key = keyList.get(keyIndex);
            Long count = typeCountMap.get(key);
            if (count <= 0) {
                typeCountMap.remove(key);
                keyList.remove(keyIndex);
            } else {
                String[] ss = key.split("_");
                updateExistsCountAndSelectedCount(typeCountMap, selectedMap, Long.valueOf(ss[0]), ss[1], 1);
                updateConfigSelectMap(configSelectMap, config, new QuestionSelectCounter(Long.valueOf(ss[0]), ss[1], 1));
                needCount--;
                count--;
                // 仅剩的一个被选择了, 从列表中清除
                if (count == 0) {
                    typeCountMap.remove(key);
                    keyList.remove(keyIndex);
                }
            }
        }
        return needCount;
    }

    /**
     * 处理分类随机、类型确定，分类确定、类型随机的题目类型。<br>
     * 这部分是最复杂的，需要考虑复杂的情况:<br>
     * 1. 指定了分类，类型随机，如果首先选择，可能会导致后面指定题型数量不够（如类型2有3个单选择题，2个多选题，类型2指定了3题，选中了2个单选题，其他配置选了2个单选题，导致数量不够。如果选择1单选2多选 +
     * 2单选，这种是足够的）<br>
     * 2. 需综合考虑，先统计一遍，看看数量是否足够，再分配
     *
     * @param partRandomList
     * @param typeCountMap
     * @param selectedMap
     * @param configSelectMap
     */
    private void processPartRandomList(List<EduRandomSelectQuestionConfig> partRandomList,
                                       Map<String, Long> typeCountMap, Map<String, QuestionSelectCounter> selectedMap,
                                       MultiValueMap<EduRandomSelectQuestionConfig, QuestionSelectCounter> configSelectMap) {

        List<EduRandomSelectQuestionConfig> specificQuestionTypeList = partRandomList.stream()
                .filter(c -> c.getCatagory() == 0L).collect(Collectors.toList());
        List<Long> specificCatagoryIdList = partRandomList.stream().filter(c -> c.getCatagory() != 0L)
                .map(EduRandomSelectQuestionConfig::getCatagory).collect(Collectors.toList());
        List<EduRandomSelectQuestionConfig> specificCatagoryList = partRandomList.stream()
                .filter(c -> c.getCatagory() != 0L).collect(Collectors.toList());

        Map<Long, Long> catagoryNeedMap = new HashMap<>();
        // 再统计确定题型、不确定分类的需求数量
        MultiValueMap<String, QuestionSelectCounter> questionTypeMap = toQuestionTypeMap(typeCountMap);

        // 先简单校验确定分类的数据是否足够。通过此校验，不表示一定能分配成功，还存在交叉的情况
        for (EduRandomSelectQuestionConfig config : partRandomList) {
            checkQuestionTypeCount(config, typeCountMap);
            if (config.getCatagory() != 0L) {
                Long typeCount = findSpecifiedTypeCount(config.getCatagory(), typeCountMap);
                Long needCount = catagoryNeedMap.get(config.getCatagory());
                needCount = needCount == null ? 0 : needCount;
                String catagory = commonTypeDao.getNameById(config.getCatagory());
                if (typeCount + needCount < config.getQuestionCount()) {
                    throw new IllegalArgumentException(
                            "分类" + catagory + "，题型随机，题目数量只有" + typeCount + "题，小于需求数量，无法生成");
                }
                catagoryNeedMap.put(config.getCatagory(), typeCount + needCount);
            } else {
                List<QuestionSelectCounter> list = questionTypeMap.get(config.getQuestionType());
                int total = 0;
                if (list == null || (total = list.stream().mapToInt(q -> q.count).sum()) < config.getQuestionCount()) {
                    throw new IllegalArgumentException(
                            "题型" + QuestionTypes.findNameByCode(config.getQuestionType()) + "，分类随机，题目数量只有" + total + "题，小于需求数量，无法生成");
                }
            }
        }
        Map<String, Long> typeNeedMap = new HashMap<>();
        specificQuestionTypeList.forEach(c -> typeNeedMap.compute(c.getQuestionType(),
                (k, v) -> v == null ? c.getQuestionCount() : v + c.getQuestionCount()));

        MultiValueMap<String, QuestionSelectCounter> typeSelectedMap = new LinkedMultiValueMap<>();
        // 优先从不在指定类型列表的数据中获取
        // 已在上面的循环中校验数据是否存在，不用担心list为null
        for (EduRandomSelectQuestionConfig config : specificQuestionTypeList) {
            List<QuestionSelectCounter> list = questionTypeMap.get(config.getQuestionType());
            // 随机分布
            Collections.shuffle(list);
            for (QuestionSelectCounter c : list) {
                if (!specificCatagoryIdList.contains(c.questionTypeId)) {
                    // 先排除本次明确指定分类的类型中
                    Long needCount = typeNeedMap.get(config.getQuestionType());
                    if (needCount <= c.count) {
                        // 满了
                        typeNeedMap.remove(config.getQuestionType());
                        QuestionSelectCounter selected = new QuestionSelectCounter(c.questionTypeId,
                                config.getQuestionType(), needCount.intValue());
                        typeSelectedMap.add(config.getQuestionType(), selected);

                        // 更新配置选择数量
                        updateConfigSelectMap(configSelectMap, config, selected);
                        break;
                    } else {
                        // 未满, 记录选中的数量
                        typeNeedMap.put(config.getQuestionType(), needCount - c.count);
                        QuestionSelectCounter selected = new QuestionSelectCounter(c.questionTypeId,
                                config.getQuestionType(), c.count);
                        typeSelectedMap.add(config.getQuestionType(), selected);

                        // 更新配置选择数量
                        updateConfigSelectMap(configSelectMap, config, selected);
                    }
                }
            }
        }

        // 首先将类型结果添加到已选列表中
        typeSelectedMap.forEach((k, v) -> {
            v.forEach(q -> selectedMap.compute(createTypeKey(q.questionTypeId, q.type), (key, counter) -> {
                // 扣除已选择的总数
                typeCountMap.compute(key, (tk, tv) -> tv - q.count);
                if (counter == null) {
                    return q;
                } else {
                    counter.count += q.count;
                    return counter;
                }
            }));
        });

        if (typeNeedMap.size() == 0) {
            // 非指定类型已经可以满足, 指定类型、题型随机的题目按需选择即可
            chooseFromCatagoryRows(
                    partRandomList.stream().filter(c -> c.getCatagory() != 0L).collect(Collectors.toList()),
                    typeCountMap, selectedMap, configSelectMap);
        } else if (typeNeedMap.size() == 1) {
            Entry<String, Long> e = typeNeedMap.entrySet().iterator().next();
            // 优先选择非此题型的题目，数量足够则完成分配，数量不够报错
            chooseFromCatagoryRowsSelectedQuestionTypeLast(
                    partRandomList.stream().filter(c -> c.getCatagory() != 0L).collect(Collectors.toList()), e.getKey(),
                    e.getValue().intValue(), typeCountMap, selectedMap, configSelectMap);
        } else {
            // 思路1: 形成一个二维矩阵T，row为catagory, column为questionType, 交叉处的值为可选的题目数量。找到2个矩阵A,B, Aij + Bij <= Tij, ∑Arj =
            // catagoryNeedCount, ∑Bic = questionTypeNeedCount. r、c表示任一指定的行、列
            // 思路2: 矩阵的每行可以看做一个 n维向量，枚举出该行所有可能的值，m行所有可能性相乘，找到第一个符合条件的，找不到符合条件，即报异常
            List<String> typeList = new ArrayList<>(typeNeedMap.keySet());
            Matrix stockM = new Matrix(specificCatagoryIdList.size(), typeNeedMap.size());
            typeCountMap.forEach((k, v) -> {
                String[] ss = k.split("_");
                Long catagory = Long.valueOf(ss[0]);
                int typeIndex = typeList.indexOf(ss[1]);
                int catagoryIndex = specificCatagoryIdList.indexOf(catagory);
                if (catagoryIndex >= 0 && typeIndex >= 0) {
                    stockM.setData(catagoryIndex, typeIndex, v.intValue());
                }
            });
            List<List<int[]>> allPossibilities = new ArrayList<>();
            for (int i = 0; i < specificCatagoryIdList.size(); i++) {
                allPossibilities
                        .add(calcEveryPossibleRow(stockM.data[i], specificCatagoryList.get(i).getQuestionCount()));
            }
            Matrix[] matches = findMatch(stockM, allPossibilities, typeNeedMap, typeList);

            for (int k = 0; k < matches.length; k++) {
                Matrix match = matches[k];
                for (int i = 0; i < match.rows; i++) {
                    for (int j = 0; j < match.columns; j++) {
                        if (match.data[i][j] > 0) {
                            updateExistsCountAndSelectedCount(typeCountMap, selectedMap, specificCatagoryIdList.get(i),
                                    typeList.get(j), match.data[i][j]);
                            updateConfigSelectMap(configSelectMap, specificCatagoryList.get(i),
                                    new QuestionSelectCounter(specificCatagoryIdList.get(i), typeList.get(j),
                                            match.data[i][j]));
                        }
                    }
                }
            }
        }
    }

    Matrix[] findMatch(Matrix stockM, List<List<int[]>> allPossibilities, Map<String, Long> typeNeedMap,
                       List<String> typeList) {
        int[] seed = new int[allPossibilities.size()];
        long maxSearchTimes = 1L; // 最多查找次数
        for (List<int[]> list : allPossibilities) {
            maxSearchTimes *= list.size();
        }
        for (int i = 0; i < maxSearchTimes; i++) {
            Matrix newM = new Matrix(allPossibilities.size(), typeList.size());
            for (int j = 0; j < allPossibilities.size(); j++) {
                newM.setRowData(j, allPossibilities.get(j).get(seed[j]));
            }
            Matrix subtract = stockM.subtract(newM);
            // 相减结果没有负数且剩余值满足类型需求
            if (subtract.noNegativeValue() && matchCondition(subtract, typeNeedMap, typeList)) {
                Matrix[] re = new Matrix[2];
                re[0] = newM;
                re[1] = findTypeMatchMatrix(subtract, typeNeedMap, typeList);
                return re;
            } else {
                seed[0]++;
                for (int j = 0; j < seed.length; j++) {
                    if (seed[j] >= allPossibilities.get(j).size() && j < allPossibilities.size() - 1) {
                        for (int k = 0; k < j; k++) {
                            // 低位清0
                            seed[k] = 0;
                        }
                        seed[j + 1]++;
                        // 向上进位
                    }
                }
            }
        }
        throw new IllegalStateException("找不到合适的随机匹配");
    }

    private Matrix findTypeMatchMatrix(Matrix subtract, Map<String, Long> typeNeedMap, List<String> typeList) {
        Matrix m = new Matrix(subtract.rows, subtract.columns);
        for (int col = 0; col < typeList.size(); col++) {
            int needCount = typeNeedMap.get(typeList.get(col)).intValue();
            for (int row = 0; row < m.rows; row++) {
                if (subtract.data[row][col] >= needCount) {
                    m.setData(row, col, needCount);
                    needCount = 0;
                    break;
                } else {
                    m.setData(row, col, subtract.data[row][col]);
                    needCount -= subtract.data[row][col];
                }
            }
            if (needCount != 0) {
                throw new IllegalStateException("找不到合适的类型匹配2");
            }
        }
        return m;
    }

    private boolean matchCondition(Matrix subtract, Map<String, Long> typeNeedMap, List<String> typeList) {
        for (int col = 0; col < typeList.size(); col++) {
            int needCount = typeNeedMap.get(typeList.get(col)).intValue();
            if (subtract.sumColumn(col) < needCount) {
                return false;
            }
        }
        return true;
    }

    /**
     * 枚举所有符合如下条件的序列re:<br>
     * 1. re[i] <= constraints[i]<br>
     * 2. ∑re[i] = sum<br>
     * 采用递归的方式计算
     *
     * @param constraints 约束的数组
     * @param sum         总和
     * @return
     */
    List<int[]> calcEveryPossibleRow(int[] constraints, int sum) {
        List<int[]> result = new ArrayList<int[]>();
        int[] seed = new int[constraints.length];
        recursivelyCalc(result, seed, constraints, sum, seed.length - 1);
        return result;
    }

    void recursivelyCalc(List<int[]> result, int[] seed, int[] constraints, int sum, int depth) {
        for (int i = 0; i <= sum && i <= constraints[depth]; i++) {
            seed[depth] = i;
            if (depth != 0) {
                recursivelyCalc(result, seed, constraints, sum, depth - 1);
            } else {
                if (sumArr(seed) == sum) {
                    result.add(seed.clone());
                }
            }
        }
    }

    private int sumArr(int[] seed) {
        int sum = 0;
        for (int i = 0; i < seed.length; i++) {
            sum += seed[i];
        }
        return sum;
    }

    private void chooseFromCatagoryRows(List<EduRandomSelectQuestionConfig> needMap, Map<String, Long> typeCountMap,
                                        Map<String, QuestionSelectCounter> selectedMap,
                                        MultiValueMap<EduRandomSelectQuestionConfig, QuestionSelectCounter> configSelectMap) {
        Random random = new Random();
        for (EduRandomSelectQuestionConfig c : needMap) {
            // 将已有题型，按照数量生成N个题型字符串，放入list中，随机选择。完全随机的效果
            List<String> questionTypeRandomList = new ArrayList<String>();
            typeCountMap.forEach((k, v) -> {
                if (k.startsWith(c.getCatagory() + "_")) {
                    String[] ss = k.split("_");
                    for (int i = 0; i < v.intValue(); i++) {
                        questionTypeRandomList.add(ss[1]);
                    }
                }
            });

            Collections.shuffle(questionTypeRandomList);
            // 选取需要的题目数量
            List<String> subList = questionTypeRandomList.subList(0, c.getQuestionCount());

            // 获取随机后的主观题数量
            long zNum = subList.stream().filter(v -> v.equals("Z")).count();

            if (c.getQuestionType().equals(DistributionQuestionTypes.RANDOM_NOT_ZG_CHOICE.getCode())) {
                // 如果包含主观题 则 重新随机
                if (zNum > 0) {
                    List<String> list = questionTypeRandomList.stream().filter(v -> !v.equals("Z")).collect(Collectors.toList());
                    Collections.shuffle(list);
                    subList = questionTypeRandomList.subList(0, c.getQuestionCount());
                }

            }
            if (c.getQuestionType().equals(DistributionQuestionTypes.RANDOM_CONTAIN_ZG_CHOICE.getCode())) {
                // 总主观题数量
                long count = questionTypeRandomList.stream().filter(v -> v.equals("Z")).count();

                if (zNum <= 0) {
                    // 随机主观题数量
                    int num = random.nextInt(c.getQuestionCount());
                    if (num > count) {
                        num = Convert.toInt(count);
                    }
                    for (int i = 0; i < num; i++) {
                        subList.set(num - 1, "Z");
                    }
                }
            }

            subList.forEach(s -> {
                updateExistsCountAndSelectedCount(typeCountMap, selectedMap, c.getCatagory(), s, 1);

                updateConfigSelectMap(configSelectMap, c, new QuestionSelectCounter(c.getCatagory(), s, 1));
            });
        }
    }

    /**
     * 优先从非questionType的分类中分配，最后从questionType分类中分配
     *
     * @param needMap
     * @param questionType
     * @param needCount
     * @param typeCountMap
     * @param selectedMap
     * @param configSelectMap
     */
    private void chooseFromCatagoryRowsSelectedQuestionTypeLast(List<EduRandomSelectQuestionConfig> needMap,
                                                                String questionType, int needCount, Map<String, Long> typeCountMap,
                                                                Map<String, QuestionSelectCounter> selectedMap,
                                                                MultiValueMap<EduRandomSelectQuestionConfig, QuestionSelectCounter> configSelectMap) {
        // 缓存的catagory, questionType剩余的题目数量
        Map<Long, Long> remainQuestionTypeMap = new HashMap<>();
        Map<Long, EduRandomSelectQuestionConfig> remainQuestionTypeConfigMap = new HashMap<>();

        // 先把指定分类的分配完，再分配指定题型的
        for (EduRandomSelectQuestionConfig c : needMap) {
            // 统计指定catagory每个题型的数量
            Map<String, Long> existsQuestionTypeCountMap = new HashMap<>();
            typeCountMap.forEach((k, v) -> {
                if (k.startsWith(c.getCatagory() + "_")) {
                    String[] ss = k.split("_");
                    existsQuestionTypeCountMap.put(ss[1], v);
                }
            });
            Long questionTypeCount = existsQuestionTypeCountMap.remove(questionType);
            if (questionTypeCount == null) {
                questionTypeCount = 0L;
            }
            int catagoryNeedCount = c.getQuestionCount();
            for (Entry<String, Long> e : existsQuestionTypeCountMap.entrySet()) {
                Long count = e.getValue();
                if (catagoryNeedCount <= count) {
                    updateExistsCountAndSelectedCount(typeCountMap, selectedMap, c.getCatagory(), e.getKey(),
                            catagoryNeedCount);
                    updateConfigSelectMap(configSelectMap, c,
                            new QuestionSelectCounter(c.getCatagory(), e.getKey(), catagoryNeedCount));
                    catagoryNeedCount = 0;
                    break;
                } else {
                    updateExistsCountAndSelectedCount(typeCountMap, selectedMap, c.getCatagory(), e.getKey(),
                            count.intValue());
                    updateConfigSelectMap(configSelectMap, c,
                            new QuestionSelectCounter(c.getCatagory(), e.getKey(), count.intValue()));
                    catagoryNeedCount -= count;
                }
            }
            if (catagoryNeedCount > questionTypeCount) {
                throw new IllegalStateException("typeNeedMap.size()==1情况，计算错误，分类" + c.getCatagory() + "需求数量:"
                        + catagoryNeedCount + ",剩余数量:" + questionTypeCount);
            }

            if (catagoryNeedCount != 0) {
                updateExistsCountAndSelectedCount(typeCountMap, selectedMap, c.getCatagory(), questionType,
                        catagoryNeedCount);

                updateConfigSelectMap(configSelectMap, c,
                        new QuestionSelectCounter(c.getCatagory(), questionType, catagoryNeedCount));
                questionTypeCount -= catagoryNeedCount;
            }
            if (questionTypeCount > 0) {
                remainQuestionTypeMap.put(c.getCatagory(), questionTypeCount);
                remainQuestionTypeConfigMap.put(c.getCatagory(), c);
            }
        }

        // 最后计算指定题型需求数量
        for (Entry<Long, Long> e : remainQuestionTypeMap.entrySet()) {
            Long count = e.getValue();
            if (count >= needCount) {
                updateExistsCountAndSelectedCount(typeCountMap, selectedMap, e.getKey(), questionType, needCount);
                updateConfigSelectMap(configSelectMap, remainQuestionTypeConfigMap.get(e.getKey()),
                        new QuestionSelectCounter(e.getKey(), questionType, needCount));
                needCount = 0;
                break;
            } else {
                needCount -= count;
                updateExistsCountAndSelectedCount(typeCountMap, selectedMap, e.getKey(), questionType,
                        count.intValue());
                updateConfigSelectMap(configSelectMap, remainQuestionTypeConfigMap.get(e.getKey()),
                        new QuestionSelectCounter(e.getKey(), questionType, count.intValue()));
            }
        }
        if (needCount != 0) {
            throw new IllegalStateException("typeNeedMap.size()==1情况，计算错误，题型" + questionType + "需求数量缺口:" + needCount);
        }
    }

    private void updateExistsCountAndSelectedCount(Map<String, Long> typeCountMap,
                                                   Map<String, QuestionSelectCounter> selectedMap, Long catagory, String questionType, int count) {
        String key = createTypeKey(catagory, questionType);
        // 更新存量
        typeCountMap.compute(key, (k, v) -> v - count);

        // 更新已选择列表
        selectedMap.compute(key, (k, v) -> {
            if (v == null) {
                return new QuestionSelectCounter(catagory, questionType, count);
            } else {
                v.count += 1;
                return v;
            }
        });
    }

    private MultiValueMap<Long, QuestionSelectCounter> toCatagirtMap(Map<String, Long> typeCountMap) {
        MultiValueMap<Long, QuestionSelectCounter> map = new LinkedMultiValueMap<Long, EduQuestionServiceImpl.QuestionSelectCounter>();
        for (Entry<String, Long> e : typeCountMap.entrySet()) {
            String[] ss = e.getKey().split("_");
            Long catagory = Long.valueOf(ss[0]);
            map.add(catagory, new QuestionSelectCounter(catagory, ss[1], e.getValue().intValue()));
        }
        return map;
    }

    private MultiValueMap<String, QuestionSelectCounter> toQuestionTypeMap(Map<String, Long> typeCountMap) {
        MultiValueMap<String, QuestionSelectCounter> map = new LinkedMultiValueMap<String, EduQuestionServiceImpl.QuestionSelectCounter>();
        for (Entry<String, Long> e : typeCountMap.entrySet()) {
            String[] ss = e.getKey().split("_");
            map.add(ss[1], new QuestionSelectCounter(Long.valueOf(ss[0]), ss[1], e.getValue().intValue()));
        }
        return map;
    }

    private Long findSpecifiedTypeCount(Long catagory, Map<String, Long> typeCountMap) {
        Long sum = 0L;
        String prefix = catagory + "_";
        for (Entry<String, Long> e : typeCountMap.entrySet()) {
            if (e.getKey().startsWith(prefix)) {
                sum += e.getValue();
            }
        }
        return sum;
    }


    public void checkQuestionTypeCount(EduRandomSelectQuestionConfig config, Map<String, Long> typeCountMap) {
        if (config.getQuestionType().equals(DistributionQuestionTypes.RANDOM_CONTAIN_ZG_CHOICE.getCode())) {
            String catagory = commonTypeDao.getNameById(config.getCatagory());
            Long zgCount = findSpecifiedZgCount(config.getCatagory(), typeCountMap);
            if (zgCount <= 0) {
                throw new IllegalArgumentException(String.format("%s，主观题数量不足，无法生成", config.getCatagory() != 0L ? "分类" + catagory : "随机"));
            }
        }
        if (config.getQuestionType().equals(DistributionQuestionTypes.RANDOM_NOT_ZG_CHOICE.getCode())) {
            String catagory = commonTypeDao.getNameById(config.getCatagory());
            Long count = findSpecifiedNotZgCount(config.getCatagory() + "", typeCountMap);
            if (count <= 0 || count < config.getQuestionCount()) {
                throw new IllegalArgumentException(String.format("%s，客观题数量不足，无法生成", config.getCatagory() != 0L ? "分类" + catagory : "随机"));
            }
        }
    }

    /**
     * 查询主观题数量
     *
     * @param catagory
     * @param typeCountMap
     * @return
     */
    private Long findSpecifiedZgCount(Long catagory, Map<String, Long> typeCountMap) {
        Long sum = 0L;
        String key = catagory + "_Z";
        for (Entry<String, Long> e : typeCountMap.entrySet()) {
            if (catagory != 0L && key.equals(e.getKey())) {
                sum += e.getValue();
            }
            if (catagory == 0L) {
                String[] split = e.getKey().split("_");
                if (split[1].equals("Z")) {
                    sum += e.getValue();
                }
            }

        }
        return sum;
    }


    private Long findSpecifiedNotZgCount(String catagory, Map<String, Long> typeCountMap) {
        Long sum = 0L;
        for (Entry<String, Long> e : typeCountMap.entrySet()) {
            String[] split = e.getKey().split("_");

            if (!catagory.equals("0") && catagory.equals(split[0]) && !split[1].equals("Z")) {
                sum += e.getValue();
            }

            if (catagory.equals("0") && !split[1].equals("Z")) {
                sum += e.getValue();
            }

        }
        return sum;
    }


    private String createTypeKey(Long catagory, String questionType) {
        return catagory + "_" + questionType;
    }

    private boolean isRandomQuestionType(String questionType) {
        return DistributionQuestionTypes.RANDOM_CONTAIN_ZG_CHOICE.getCode().equals(questionType) ||
                DistributionQuestionTypes.RANDOM_NOT_ZG_CHOICE.getCode().equals(questionType);
    }

    public static class QuestionSelectWrapper {
        EduQuestion q;
        EduRandomSelectQuestionConfig config;
        int configIndex;
    }

    // 记录每种类型选了多少条
    static class QuestionSelectCounter {
        QuestionSelectCounter() {
        }

        public QuestionSelectCounter(long questionTypeId, String type, int count) {
            super();
            this.questionTypeId = questionTypeId;
            this.type = type;
            this.count = count;
        }

        long questionTypeId;
        String type;
        int count;

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + (int) (questionTypeId ^ (questionTypeId >>> 32));
            result = prime * result + ((type == null) ? 0 : type.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            QuestionSelectCounter other = (QuestionSelectCounter) obj;
            if (questionTypeId != other.questionTypeId)
                return false;
            if (type == null) {
                if (other.type != null)
                    return false;
            } else if (!type.equals(other.type))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return String.format(" questionTypeId=%s, type=%s, count=%s", questionTypeId, type, count);
        }

    }

    private static class Matrix {
        int[][] data;
        int rows, columns;

        Matrix(int rows, int columns) {
            data = new int[rows][columns];
            this.rows = rows;
            this.columns = columns;
        }

        public void setRowData(int row, int[] rd) {
            for (int i = 0; i < data[row].length; i++) {
                data[row][i] = rd[i];
            }
        }

        public void setData(int row, int col, int data) {
            this.data[row][col] = data;
        }

        public int sumRow(int row) {
            int sum = 0;
            for (int i = 0; i < data[row].length; i++) {
                sum += data[row][i];
            }
            return sum;
        }

        public int sumColumn(int col) {
            int sum = 0;
            for (int i = 0; i < data.length; i++) {
                sum += data[i][col];
            }
            return sum;
        }

        public Matrix subtract(Matrix m) {
            Matrix result = new Matrix(rows, columns);
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < columns; j++) {
                    result.data[i][j] = this.data[i][j] - m.data[i][j];
                }
            }
            return result;
        }

        public boolean noNegativeValue() {
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < columns; j++) {
                    if (this.data[i][j] < 0) {
                        return false;
                    }
                }
            }
            return true;
        }
    }

    @Override
    public List<EduRandomSelectQuestionBean> randomSelectAndConvert(List<EduRandomSelectQuestionConfig> configs) {
        List<QuestionSelectWrapper> list = randomSelectWrapper(configs);
        return list.stream().map(this::toEduRandomSelectQuestionBean).collect(Collectors.toList());
    }

    EduRandomSelectQuestionBean toEduRandomSelectQuestionBean(QuestionSelectWrapper w) {
        EduRandomSelectQuestionBean b = new EduRandomSelectQuestionBean();
        BeanUtils.copyProperties(w.q, b);
        b.setConfigIndex(w.configIndex);
        b.setScore(w.config.getScore());
        b.setTypeName(QuestionTypes.findNameByCode(w.q.getType()));
        b.setQuestionTypeName(commonTypeService.getNameById(w.q.getQuestionTypeId()));
        return b;
    }
}
